/*
 * JGrass - Free Open Source Java GIS http://www.jgrass.org 
 * (C) HydroloGIS - www.hydrologis.com 
 * 
 * This library is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Library General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option) any
 * later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Library General Public License
 * along with this library; if not, write to the Free Foundation, Inc., 59
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
package org.jgrasstools.gears.io.dxfdwg.libs.dwg.utils;

import java.awt.geom.Point2D;
import java.util.Vector;

/**
 * This class allows to obtain arcs and circles given by the most usual parameters, in a
 * Gis geometry model. In this model, an arc or a circle is given by a set of points that
 * defines it shape
 * 
 * @author jmorell
 */
public class GisModelCurveCalculator {
    
    /**
     * This method calculates an array of Point2D that represents a circle. The distance
     * between it points is 1 angular unit 
     * 
     * @param c Point2D that represents the center of the circle
     * @param r double value that represents the radius of the circle
     * @return Point2D[] An array of Point2D that represents the shape of the circle
     */
	public static Point2D[] calculateGisModelCircle(Point2D c, double r) {
		Point2D[] pts = new Point2D[360];
		int angulo = 0;
		for (angulo=0; angulo<360; angulo++) {
			pts[angulo] = new Point2D.Double(c.getX(), c.getY());
			pts[angulo].setLocation(pts[angulo].getX() + r * Math.sin(angulo*Math.PI/(double)180.0), pts[angulo].getY() + r * Math.cos(angulo*Math.PI/(double)180.0));
		}
		return pts;
	}
	
    /**
     * This method calculates an array of Point2D that represents a ellipse. The distance
     * between it points is 1 angular unit 
     * 
     * @param center Point2D that represents the center of the ellipse
     * @param majorAxisVector Point2D that represents the vector for the major axis
     * @param axisRatio double value that represents the axis ratio
	 * @param initAngle double value that represents the start angle of the ellipse arc
	 * @param endAngle double value that represents the end angle of the ellipse arc
     * @return Point2D[] An array of Point2D that represents the shape of the ellipse
     */
	public static Point2D[] calculateGisModelEllipse(Point2D center, Point2D majorAxisVector, double axisRatio, double initAngle, double endAngle) {
		Point2D majorPoint = new Point2D.Double(center.getX()+majorAxisVector.getX(), center.getY()+majorAxisVector.getY());
	    double orientation  = Math.atan(majorAxisVector.getY()/majorAxisVector.getX());
	    double semiMajorAxisLength = center.distance(majorPoint);
		double semiMinorAxisLength = semiMajorAxisLength*axisRatio;
	    double eccentricity = Math.sqrt(1-((Math.pow(semiMinorAxisLength, 2))/(Math.pow(semiMajorAxisLength, 2))));
		int isa = (int)initAngle;
		int iea = (int)endAngle;
		double angulo;
		Point2D[] pts;
		if (initAngle <= endAngle) {
			pts = new Point2D[(iea-isa)+2];
			angulo = initAngle;
			double r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
		    double x = r*Math.cos(angulo*Math.PI/(double)180.0);
		    double y = r*Math.sin(angulo*Math.PI/(double)180.0);
		    double xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
		    double yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
			pts[0] = new Point2D.Double(center.getX() + xrot, center.getY() + yrot);
			for (int i=1; i<=(iea-isa)+1; i++) {
				angulo = (double)(isa+i);
				r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
			    x = r*Math.cos(angulo*Math.PI/(double)180.0);
			    y = r*Math.sin(angulo*Math.PI/(double)180.0);
			    xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
			    yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
			    pts[i] = new Point2D.Double(center.getX() + xrot, center.getY() + yrot);
			}
			angulo = endAngle;
			r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
		    x = r*Math.cos(angulo*Math.PI/(double)180.0);
		    y = r*Math.sin(angulo*Math.PI/(double)180.0);
		    xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
		    yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
		    pts[(iea-isa)+1] = new Point2D.Double(center.getX() + xrot, center.getY() + yrot);
		} else {
			pts = new Point2D[(360-isa)+iea+2];
			angulo = initAngle;
			double r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
		    double x = r*Math.cos(angulo*Math.PI/(double)180.0);
		    double y = r*Math.sin(angulo*Math.PI/(double)180.0);
		    double xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
		    double yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
		    pts[0] = new Point2D.Double(center.getX() + r*Math.cos(angulo*Math.PI/(double)180.0), center.getY() + r*Math.sin(angulo*Math.PI/(double)180.0));
			for (int i=1; i<=(360-isa); i++) {
				angulo = (double)(isa+i);
				r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
			    x = r*Math.cos(angulo*Math.PI/(double)180.0);
			    y = r*Math.sin(angulo*Math.PI/(double)180.0);
			    xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
			    yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
			    pts[i] = new Point2D.Double(center.getX() + xrot, center.getY() + yrot);
			}
			for (int i=(360-isa)+1; i<=(360-isa)+iea; i++) {
				angulo = (double)(i-(360-isa));
				r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
			    x = r*Math.cos(angulo*Math.PI/(double)180.0);
			    y = r*Math.sin(angulo*Math.PI/(double)180.0);
			    xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
			    yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
			    pts[i] = new Point2D.Double(center.getX() + xrot, center.getY() + yrot);
			}
			angulo = endAngle;
			r = semiMinorAxisLength/Math.sqrt(1-((Math.pow(eccentricity, 2))*(Math.pow(Math.cos(angulo*Math.PI/(double)180.0), 2))));
		    x = r*Math.cos(angulo*Math.PI/(double)180.0);
		    y = r*Math.sin(angulo*Math.PI/(double)180.0);
		    xrot = x*Math.cos(orientation) - y*Math.sin(orientation);
		    yrot = x*Math.sin(orientation) + y*Math.cos(orientation);
		    pts[(360-isa)+iea+1] = new Point2D.Double(center.getX() + xrot, center.getY() + yrot);
		}
		return pts;
	}
	
	/**
     * This method calculates an array of Point2D that represents an arc. The distance
     * between it points is 1 angular unit 
	 * 
     * @param c Point2D that represents the center of the arc
     * @param r double value that represents the radius of the arc
	 * @param sa double value that represents the start angle of the arc
	 * @param ea double value that represents the end angle of the arc
     * @return Point2D[] An array of Point2D that represents the shape of the arc
	 */
	public static Point2D[] calculateGisModelArc(Point2D c, double r, double sa, double ea) {
		int isa = (int)sa;
		int iea = (int)ea;
		double angulo;
		Point2D[] pts;
		if (sa <= ea) {
			pts = new Point2D[(iea-isa)+2];
			angulo = sa;
			pts[0] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
			for (int i=1; i<=(iea-isa)+1; i++) {
				angulo = (double)(isa+i);
				pts[i] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
			}
			angulo = ea;
			pts[(iea-isa)+1] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
		} else {
			pts = new Point2D[(360-isa)+iea+2];
			angulo = sa;
			pts[0] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
			for (int i=1; i<=(360-isa); i++) {
				angulo = (double)(isa+i);
				pts[i] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
			}
			for (int i=(360-isa)+1; i<=(360-isa)+iea; i++) {
				angulo = (double)(i-(360-isa));
				pts[i] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
			}
			angulo = ea;
			pts[(360-isa)+iea+1] = new Point2D.Double(c.getX() + r * Math.cos(angulo*Math.PI/(double)180.0), c.getY() + r * Math.sin(angulo*Math.PI/(double)180.0));
		}
		return pts;
	}
	
	/**
	 * This method applies an array of bulges to an array of Point2D that defines a
	 * polyline. The result is a polyline with the input points with the addition of the
	 * points that define the new arcs added to the polyline
	 * 
	 * @param newPts Base points of the polyline
	 * @param bulges Array of bulge parameters
	 * @return Polyline with a new set of arcs added and defined by the bulge parameters
	 */
	public static Point2D[] calculateGisModelBulge(Point2D[] newPts, double[] bulges) {
		Vector ptspol = new Vector();
		Point2D init = new Point2D.Double();
		Point2D end = new Point2D.Double();
		for (int j=0; j<newPts.length; j++) {
			init = newPts[j];
			if (j!=newPts.length-1) end = newPts[j+1];
			if (bulges[j]==0 || j==newPts.length-1 || (init.getX()==end.getX() && init.getY()==end.getY())) {
				ptspol.add(init);
			} else {
				ArcFromBulgeCalculator arcCalculator = new ArcFromBulgeCalculator(init, end, bulges[j]);
				Vector arc = arcCalculator.getPoints(1);
				if (bulges[j]<0) {
					for (int k=arc.size()-1; k>=0; k--) {
						ptspol.add(arc.get(k));
					}
					ptspol.remove(ptspol.size()-1);
				} else {
					for (int k=0;k<arc.size();k++) {
						ptspol.add(arc.get(k));
					}
					ptspol.remove(ptspol.size()-1);
				}
			}
		}
		Point2D[] points = new Point2D[ptspol.size()];
		for (int j=0;j<ptspol.size();j++) {
			points[j] = (Point2D)ptspol.get(j);
		}
		return points;
	}
}
